<html>

<head>
<title>Flexible Precision Images</title>
<style type="text/css"><!--tt { font-size: 10pt } pre { font-size: 10pt }--></style>
</head>

<body bgcolor="#ffffff" text="#000000" link="#000080" vlink="#800000" alink="#0000ff">

<table border="0" cellpadding="0" cellspacing="0" bgcolor="#d0d0d0">
  <tr>
    <td width="120" align="left"><a href="ilbm.html"><img width="96" height="20" border="0"
    src="../images/navlt.gif" alt="IFF (ILBM) Images"></a></td>
    <td width="96" align="left"><a href="eaiff85.html"><img width="64" height="20" border="0"
    src="../images/navrt.gif" alt="EA IFF 85"></a></td>
    <td width="96" align="left"><a href="../filefmts.html"><img width="56" height="20"
    border="0" src="../images/navup.gif" alt="File Formats"></a></td>
    <td width="288" align="right"><a href="../index.html"><img width="230" height="20"
    border="0" src="../images/proglw.gif" alt="Table of Contents"></a></td>
  </tr>
</table>

<table border="0" cellpadding="0" cellspacing="0">
  <tr>
    <td width="600"><br>
    <h3>Flexible Precision Images</h3>
    <p><small>December 3, 2000</small></p>
    <p>This document describes the FPBM (Flexible Precision Buffer Map) file format for images
    and animations introduced with LightWave&reg; 6.0.<ul>
      <li><a href="#intro">Introduction</a></li>
      <li><a href="#chunk">IFF Chunks</a></li>
      <li><a href="#basics">Data Basics</a></li>
      <li><a href="#structure">File Structure</a></li>
      <li><a href="#fphd">File Header</a></li>
      <li><a href="#flex">Frame Header</a></li>
      <li><a href="#lyhd">Layer Header</a></li>
      <li><a href="#layr">Layer Data</a></li>
      <li><a href="#rle">Run-Length Compression</a></li>
      <li><a href="#delta">Delta Compression</a></li>
      <li><a href="#examples">Example Source Code</a></li>
    </ul>
    <p><strong><a name="intro">Introduction</a></strong></p>
    <p>An <em>image</em> is a rectangular array of values. The image data generated by
    LightWave&reg; includes not only the red, green and blue levels of each pixel in the rendered
    image, but also values at each pixel for the alpha level, z-depth, shading, reflectivity,
    surface normal, 2D motion, and other buffers used during rendering. Most of these
    quantities are represented internally as floating-point numbers, and all may change over
    time. Existing image and animation file formats are inadequate for storing all of this
    information, which is the motivation for the new FPBM format.</p>
    <p>The data channels in an FPBM are called <em>layer</em>s, and each layer can store
    values as 8-bit or 16-bit integers or as 32-bit floating-point numbers. A set of these
    layers for a given animation time is called a <em>frame</em>. An FPBM containing a single
    frame is a still image, and one containing a time sequence of frames is an animation.</p>
    <p>(The descriptions here of the animation features of FPBM should be considered
    preliminary. They haven't been implemented in LightWave&reg; yet.)</p>
    <p><strong><a name="chunk">Chunks</a></strong></p>
    <p>The FPBM format is based on the metaformat for binary files described in &quot;<a
    href="eaiff85.html">EA IFF 85 Standard for Interchange Format Files</a>.&quot; (See also <a
    href="ilbm.html">ILBM</a>, an earlier IFF image format.) The basic structural element in
    an IFF file is the <em>chunk</em>. A chunk consists of a four-byte ID tag, a four-byte
    chunk size, and <em>size</em> bytes of data. If the size is odd, the chunk is followed by
    a 0 pad byte, so that the next chunk begins on an even byte boundary. (The pad byte isn't
    counted in the size.)</p>
    <p>A chunk ID is a sequence of 4 bytes containing 7-bit ASCII values, usually upper-case
    printable characters, used to identify the chunk's data. ID tags can be interpreted as
    unsigned integers for comparison purposes. They're typically constructed using macros like
    the following. </p>
    <pre>   #define CKID_(a,b,c,d) (((a)&lt;&lt;24)|((b)&lt;&lt;16)|((c)&lt;&lt;8)|(d))
   #define ID_FORM CKID_('F','O','R','M')
   #define ID_FPBM CKID_('F','P','B','M')
   ...</pre>
    <p>FPBM files start with the four bytes &quot;<tt>FORM</tt>&quot; followed by a four-byte
    integer giving the length of the file (minus 8) and the four byte ID &quot;<tt>FPBM</tt>&quot;.
    The remainder of the file is a collection of chunks containing layer data. </p>
    <p>To be read, IFF files must be <em>parsed</em>. FPBM files are pretty uniform, but in
    general the order in which chunks can occur in an IFF file isn't fixed. You may encounter
    chunks or layer types that aren't defined here, which you should be prepared to skip
    gracefully if you don't understand them. You can do this by using the chunk size to seek
    to the next chunk. And you may encounter chunk sizes that differ from those implied here.
    Readers must respect the chunk size. Missing data should be given default values, and
    extra data, which the reader presumably doesn't understand, should be skipped.</p>
    <p><strong><a name="basics">Data Basics</a></strong></p>
    <p>The data in an FPBM will be described in this document using C language conventions.
    Chunks will be represented as structures, and the values within each structure will be
    defined as the C basic types <em>short</em> or <em>float</em>. As used here, a short is a
    signed, two's complement, 16-bit integer, and a float is a 32-bit IEEE floating-point
    number. All data in an FPBM is written in big-endian (Motorola, Internet) byte order.
    Programs running in environments (primarily Microsoft Windows) that use a different byte
    order must swap bytes after reading and before writing.</p>
    <p><strong><a name="structure">File Structure</a></strong></p>
    <p>Structurally, FPBMs are quite simple.</p>
    <pre>   <strong>FORM</strong> formsize <strong>FPBM</strong>
   <strong>FPHD</strong> 28 FPHeader
   <em>for each frame</em>
      <strong>FLEX</strong> 2 numLayers
      <em>for each layer</em>
         <strong>LYHD</strong> 20 LayerHeader
         <strong>LAYR</strong> datasize data</pre>
    <p>The header is followed by one or more frames. Each frame begins with a layer count, and
    this is followed by the layers. Each layer begins with a header describing the data it
    contains.</p>
    <p>The following sections describe the <tt>FPHD</tt>, <tt>FLEX</tt> and <tt>LYHD</tt>
    chunks.</p>
    <p><strong><a name="fphd">FPHD - Flexible Precision Header</a></strong></p>
    <p>The FPHeader contains information that applies globally to all of the frames in the
    file. It appears first in the file, after the <tt>FORM</tt> prefix and before the first
    frame.</p>
    <pre>   typedef struct st_FPHeader {
      short <strong>width</strong>;
      short <strong>height</strong>;
      short <strong>numLayers</strong>;
      short <strong>numFrames</strong>;
      short <strong>numBuffers</strong>;
      short <strong>flags</strong>;
      short <strong>srcBytesPerLayerPixel</strong>;
      short <strong>pad2</strong>;
      float <strong>pixelAspect</strong>;
      float <strong>pixelWidth</strong>;
      float <strong>framesPerSecond</strong>;
   } FPHeader;</pre>
    <dl>
      <tt>
      <dt><strong>width, height</strong></dt>
      </tt>
      <dd>Pixel dimensions of the image.</dd>
      <tt>
      <dt><br>
        <strong>numLayers</strong></dt>
      </tt>
      <dd>The maximum number of layers per frame. Some layers may not be stored for all frames,
        since their contents may not differ from frame to frame.</dd>
      <tt>
      <dt><br>
        <strong>numFrames</strong></dt>
      </tt>
      <dd>Number of frames. For still images, this will be 1.</dd>
      <tt>
      <dt><br>
        <strong>numBuffers</strong></dt>
      </tt>
      <dd>Number of animation buffers. This affects the interpretation of delta encoded layer
        data. When the file is written for single buffered playback, the number of buffers is 1
        and the deltas are relative to the previous frame. For double buffered playback, the
        deltas are relative to the frame &quot;two frames back,&quot; since the new frame is drawn
        over the contents of the back buffer. This field is ignored for still images.</dd>
      <tt>
      <dt><br>
        <strong>flags</strong></dt>
      </tt>
      <dd>One or more of the following flag values, combined using bitwise-or. <dl>
          <tt>
          <dt><br>
            Source_Int (0 &lt;&lt; 0)<br>
            Source_FP (1 &lt;&lt; 0)</dt>
          </tt>
          <dd>The &quot;natural&quot; representation of the data, or the way the data was stored
            before being written to the file, either integer (the default) or floating-point. This can
            differ from the way the data is actually stored in the file (specified for each layer in
            the LayerHeader <tt>flags</tt> field). Readers may wish to restore the data to its
            original representation using this information. It can also be used to indicate the
            precision of the source data.</dd>
          <tt>
          <dt><br>
            InterlaceFlag (1 &lt;&lt; 1)</dt>
          </tt>
          <dd>Scanlines should be interlaced (field rendered) for playback. (The actual interlace
            state is stored for each layer in the LayerHeader.)</dd>
        </dl>
      </dd>
      <tt>
      <dt><br>
        <strong>srcBytesPerLayerPixel</strong></dt>
      </tt>
      <dd>Use this in combination with the <tt>Source_Int</tt> and <tt>Source_FP</tt> flags to
        determine the &quot;natural&quot; data type for the data in the file, or the type in which
        the data was stored before it was written to the file. The most common values for this
        field are 1, 2 and 4. The actual size of a pixel in each layer is in the LayerHeader's <tt>bytesPerLayerPixel</tt>
        field.</dd>
      <tt>
      <dt><br>
        <strong>pad2</strong></dt>
      </tt>
      <dd>Reserved for future use.</dd>
      <tt>
      <dt><br>
        <strong>pixelAspect</strong></dt>
      </tt>
      <dd>Pixel aspect ratio expressed as width divided by height.</dd>
      <tt>
      <dt><br>
        <strong>pixelWidth</strong></dt>
      </tt>
      <dd>Pixel width in millimeters. This fixes the size of the image for print. To calculate
        horizontal and vertical DPI (dots per inch) from this value, <pre>hdpi = 25.4 / pixelWidth
vdpi = 25.4 / (pixelWidth * pixelAspect)</pre>
      </dd>
      <tt>
      <dt><strong>framesPerSecond</strong></dt>
      </tt>
      <dd>Number of frames per second for animations. Writers may set this to 0.0 for still
        images.</dd>
    </dl>
    <p><strong><a name="flex">FLEX - Frame Header</a></strong></p>
    <p>The FrameHeader appears at the start of each frame. This chunk may grow in the future
    to include other information.</p>
    <pre>   typedef struct st_FrameHeader {
      short <strong>numLayers</strong>;
   } FrameHeader;</pre>
    <dl>
      <tt>
      <dt><strong>numLayers</strong></dt>
      </tt>
      <dd>Number of layers in this frame.</dd>
    </dl>
    <p><strong><a name="lyhd">LYHD - Layer Header</a></strong></p>
    <p>The LayerHeader appears at the start of each layer to describe the layer's contents.</p>
    <pre>   typedef struct st_LayerHeader {
      short <strong>flags</strong>;
      short <strong>layerType</strong>;
      short <strong>bytesPerLayerPixel</strong>;
      short <strong>compression</strong>;
      float <strong>blackPoint</strong>;
      float <strong>whitePoint</strong>;
      float <strong>gamma</strong>;
   } LayerHeader; </pre>
    <dl>
      <tt>
      <dt><strong>flags</strong></dt>
      </tt>
      <dd>One or more of the following flags, combined using bitwise-or. <dl>
          <tt>
          <dt><br>
            Layer_Int (0 &lt;&lt; 0)<br>
            Layer_FP (1 &lt;&lt; 0)</dt>
          </tt>
          <dd>Data in the layer is integer (the default) or floating-point.</dd>
          <tt>
          <dt><br>
            Layer_Interlace (1 &lt;&lt; 1)</dt>
          </tt>
          <dd>Scanlines are interlaced (field rendered).</dd>
          <tt>
          <dt><br>
            Layer_EvenField (0 &lt;&lt; 2)<br>
            Layer_OddField (1 &lt;&lt; 2)</dt>
          </tt>
          <dd>Field dominance for interlaced layers. This indicates which field is displayed first in
            time.</dd>
        </dl>
      </dd>
      <tt>
      <dt><br>
        <strong>layerType</strong></dt>
      </tt>
      <dd>The data channel contained in the layer. Possible values include <dl>
          <tt>
          <dt><br>
            Layer_MONO 0</dt>
          </tt>
          <dd>Monochrome (grayscale) image channel.</dd>
          <tt>
          <dt><br>
            Layer_RED 1<br>
            Layer_GREEN 2<br>
            Layer_BLUE 3<br>
            Layer_ALPHA 4</dt>
          </tt>
          <dd>Color and alpha channels.</dd>
          <tt>
          <dt><br>
            Layer_OBJECT 5</dt>
          </tt>
          <dd>Object ID.</dd>
          <tt>
          <dt><br>
            Layer_SURFACE 6</dt>
          </tt>
          <dd>Surface or material ID.</dd>
          <tt>
          <dt><br>
            Layer_COVERAGE 7</dt>
          </tt>
          <dd>Object transparency/antialiasing.</dd>
          <tt>
          <dt><br>
            Layer_ZDEPTH 8<br>
            Layer_WDEPTH 9</dt>
          </tt>
          <dd>The Z depth is the distance from the camera to the nearest object visible in a pixel.
            Strictly speaking, this is the perpendicular distance from the plane defined by the
            camera's position and view vector. The W depth buffer contains the inverse of Z.</dd>
          <tt>
          <dt><br>
            Layer_GEOMETRY 10</dt>
          </tt>
          <dd>The values in this buffer are the dot-products of the surface normals with the eye
            vector (or the cosine of the angle of the surfaces to the eye). They reveal something
            about the underlying shape of the objects in the image. Where the value is 1.0, the
            surface is facing directly toward the camera, and where it's 0, the surface is edge-on to
            the camera.</dd>
          <tt>
          <dt><br>
            Layer_SHADOW 11</dt>
          </tt>
          <dd>Indicates where shadows are falling in the final image. It may also be thought of as an
            illumination map, showing what parts of the image are visible to the lights in the scene.</dd>
          <tt>
          <dt><br>
            Layer_SHADING 12</dt>
          </tt>
          <dd>A picture of the diffuse shading and specular highlights applied to the objects in the
            scene. This is a component of the rendering calculations that depends solely on the angle
            of incidence of the lights on a surface. It doesn't include the effects of explicit shadow
            calculations.</dd>
          <tt>
          <dt><br>
            Layer_DFSHADING 13<br>
            Layer_SPSHADING 14</dt>
          </tt>
          <dd>Like the <tt>Layer_SHADING</tt> buffer, but these store the amount of diffuse and
            specular shading (highlighting) separately, rather than adding them together.</dd>
          <tt>
          <dt><br>
            Layer_TEXTUREU 15<br>
            Layer_TEXTUREV 16<br>
            Layer_TEXTUREW 17</dt>
          </tt>
          <dd>Texture coordinates.</dd>
          <tt>
          <dt><br>
            Layer_NORMALX 18<br>
            Layer_NORMALY 19<br>
            Layer_NORMALZ 20</dt>
          </tt>
          <dd>Normal vector. This is the geometric normal of the object surface visible in each pixel.</dd>
          <tt>
          <dt><br>
            Layer_REFLECT 21</dt>
          </tt>
          <dd>Reflection.</dd>
          <tt>
          <dt><br>
            Layer_MOTIONX 22<br>
            Layer_MOTIONY 23</dt>
          </tt>
          <dd>Support for 2D vector-based motion blur. These buffers contain the pixel distance moved
            by the item visible in each pixel. The amount of movement depends on the camera exposure
            time and includes the effects of the camera's motion.</dd>
        </dl>
      </dd>
      <tt>
      <dt><br>
        <strong>bytesPerLayerPixel</strong></dt>
      </tt>
      <dd>Number of bytes per pixel, usually 1, 2 or 4.</dd>
      <tt>
      <dt><br>
        <strong>compression</strong></dt>
      </tt>
      <dd>One of the following compression codes.<dl>
          <tt>
          <dt><br>
            NoCompression 0</dt>
          </tt>
          <dd>Data is uncompressed.</dd>
          <tt>
          <dt><br>
            HorizontalRLE 1<br>
            VerticalRLE 3</dt>
          </tt>
          <dd>Run-length encoding (RLE). The horizontal type is identical to the <tt>byteRun1</tt> RLE
            encoding used in <a href="ilbm.html">ILBM</a> and the output of the Macintosh <tt>PackBits</tt>
            function. The vertical type compresses along columns rather than rows. The compressor
            treats the data as a sequence of bytes, regardless of the data type of the layer's values.</dd>
          <tt>
          <dt><br>
            HorizontalDelta 2<br>
            VerticalDelta 4</dt>
          </tt>
          <dd>Delta encoding for animation. Only the parts of the image that differ from a previous
            frame are written.</dd>
        </dl>
        <p>The RLE and delta methods are described in more detail below.</p>
      </dd>
      <tt>
      <dt><strong>blackPoint, whitePoint</strong></dt>
      </tt>
      <dd>The nominal minimum and maximum buffer levels. These define the dynamic range of the
        data in the layer. Typical values for RGB layers are 0.0 and 1.0.</dd>
      <tt>
      <dt><br>
        <strong>gamma</strong></dt>
      </tt>
      <dd>Linearity of the data. This and the black and white points are used primarily to encode
        RGB levels for different display devices. The default is 1.0.</dd>
    </dl>
    <p><strong><a name="layr">Layer Data</a></strong></p>
    <p>The data for a layer is written in a <tt>LAYR</tt> chunk that immediately follows the
    layer's LayerHeader. The data is a rectangular array of values. The origin is the top left
    corner, and before compression, values are stored from left to right, and rows from top to
    bottom. No padding is added to the end of any row.</p>
    <p>When the compression type is <tt>NoCompression</tt>, this is also how the layer is
    written in the file. The number of bytes in one row is</p>
    <pre>   rowbytes = LayerHeader.bytesPerLayerPixel * FPHeader.width;</pre>
    <p>The number of rows is <tt>FPHeader.Height</tt>, and the total number of bytes of layer
    data (and the <tt>LAYR</tt> chunk size) is</p>
    <pre>   layerbytes = rowbytes * FPHeader.height;</pre>
    <p><strong><a name="rle">RLE Compression</a></strong></p>
    <p>The following psuedocode illustrates how RLE-compressed bytes are unpacked. </p>
    <pre>   loop
      read the next source byte into n
      if n &gt;= 0
         copy the next n + 1 bytes literally
      else if n &lt; 0
         replicate the next byte -n + 1 times
   until the row or column is full</pre>
    <p>The unpacker reads from the source (compressed data in a <tt>LAYR</tt> chunk) and
    writes to a destination (a memory buffer). For horizontal RLE, the destination pointer is
    incremented by 1 for each decoded byte, while for vertical RLE, the destination pointer is
    incremented by <tt>rowbytes</tt> bytes.</p>
    <p>Each row (or column) is separately packed. In other words, runs never cross rows (or
    columns).</p>
    <p>In the inverse routine (the packer), it's best to encode a 2 byte repeat run as a
    replicate run except when preceded and followed by a literal run, in which case it's best
    to merge the three into one literal run. Always encode 3 byte repeats as replicate runs.</p>
    <p><strong><a name="delta">Delta Compression</a></strong></p>
    <p>The delta compression method uses RLE, but it adds a mechanism for skipping bytes that
    haven't changed. This is used when storing animation frames. The skipped bytes retain the
    values stored there by a previous frame.</p>
    <pre>   loop
      read the next source byte into nc
      if nc &lt; 0
         skip ahead -nc columns
      else
         for i = 0 to nc
            read the next source byte into nr
            if nr &lt; 0
               skip ahead -nr rows
            else
               unpack rle encoded span of size nr + 1
   until the layer is full</pre>
    <p><strong><a name="examples">Example Code</a></strong></p>
    <p>The <tt>unpackRLE</tt> function decodes RLE compressed data. <strong><tt>psrc</tt></strong>
    points to the source pointer. The function advances the source pointer as it decodes the
    compressed bytes. <strong><tt>dst</tt></strong> is the destination buffer where decoded
    bytes are written. <strong><tt>size</tt></strong> is the RLE span, or the number of
    destination bytes that should be produced. This is typically <tt>rowbytes</tt> for
    horizontal RLE and <tt>FPHeader.Height</tt> for vertical RLE. <strong><tt>step</tt></strong>
    is the number of bytes that the destination pointer should be moved after each decoded
    byte is written, typically 1 for horizontal and <tt>rowbytes</tt> for vertical. The
    function returns <tt>TRUE</tt> if it succeeds and <tt>FALSE</tt> otherwise.</p>
    <pre>   int unpackRLE( char **psrc, char *dst, int size, int step )
   {
      int c, n;
      char *src = *psrc;

      while ( size &gt; 0 ) {
         n = *src++;

         if ( n &gt;= 0 ) {
            ++n;
            size -= n;
            if ( size &lt; 0 ) return FALSE;
            while ( n-- ) {
               *dst = *src++;
               dst += step;
            }
         }
         else {
            n = -n + 1;
            size -= n;
            if ( size &lt; 0 ) return FALSE;
            c = *src++;
            while ( n-- ) {
               *dst = c;
               dst += step;
            }
         }
      }
      *psrc = src;
      return TRUE;
   }</pre>
    <p>The <tt>packRLE</tt> function reads uncompressed bytes from the source buffer and
    writes encoded bytes to the destination. It returns the number of bytes written to the
    destination (the packed size of the source bytes).</p>
    <pre>   #define DUMP    0
   #define RUN     1
   #define MINRUN  3
   #define MAXRUN  128
   #define MAXDUMP 128

   int packRLE( char *src, char *dst, int size, int step )
   {
      char c, lastc;
      int
         mode = DUMP,
         rstart = 0,
         putsize = 0,
         sp = 1,
         i;

      lastc = *src;
      size--;

      while ( size &gt; 0 ) {
         c = *( src + sp * step );
         sp++;
         size--;

         switch ( mode ) {
            case DUMP:
               if ( sp &gt; MAXDUMP ) {
                  *dst++ = sp - 2;
                  for ( i = 0; i &lt; sp - 1; i++ )
                     *dst++ = *( src + i * step );
                  putsize += sp;
                  src += ( sp - 1 ) * step;
                  sp = 1;
                  rstart = 0;
                  break;
               }

               if ( c == lastc ) {
                  if (( sp - rstart ) &gt;= MINRUN ) {
                     if ( rstart &gt; 0 ) {
                        *dst++ = rstart - 1;
                        for ( i = 0; i &lt; rstart; i++ )
                           *dst++ = *( src + i * step );
                        putsize += rstart + 1;
                     }
                     mode = RUN;
                  }
                  else if ( rstart == 0 ) mode = RUN;
               }
               else rstart = sp - 1;
               break;

            case RUN:
               if (( c != lastc ) || ( sp - rstart &gt; MAXRUN )) {
                  *dst++ = rstart + 2 - sp;
                  *dst++ = lastc;
                  putsize += 2;
                  src += ( sp - 1 ) * step;
                  sp = 1;
                  rstart = 0;
                  mode = DUMP;
               }
         }
         lastc = c;
      }

      switch ( mode ) {
         case DUMP:
            *dst++ = sp - 1;
            for ( i = 0; i &lt; sp; i++ )
               *dst++ = *( src + i * step );
            putsize += sp + 1;
            break;

         case RUN:
            *dst++ = rstart + 1 - sp;
            *dst   = lastc;
            putsize += 2;
      }

      return putsize;
   }</pre>
    <p>The <tt>unpackDelta</tt> function decodes delta-compressed data. After skipping to a
    part of the layer containing changes, it calls <tt>unpackRLE</tt>.</p>
    <pre>   int unpackDelta( char *src, char *dst, int size, int vstep,
      int hstep )
   {
      int n, nn;

      while ( size &gt; 0 ) {
         n = *src++;
         --size;

         if ( n &lt; 0 )
            dst += -n * vstep;
         else {
            for ( ; n &gt;= 0; n-- ) {
               nn = *src++;
               --size;
               if ( nn &lt; 0 )
                  nn = -nn;
               else {
                  ++nn;
                  if ( !unpackRLE( &amp;src, dst, nn, hstep ))
                     return FALSE;
               }
               dst += nn * hstep;
            }
         }
      }

      return TRUE;
   }</pre>
    </td>
  </tr>
</table>
</body>
</html>
